package net.chrisrichardson.ec2deployer.cluster

import groovy.xml.MarkupBuilder
import java.beans.BeanInfo
import java.beans.Introspector
import java.beans.PropertyDescriptor
import java.lang.reflect.ParameterizedType

class ClusterDefinitionManager {

    def getHomeDir() {
        def homeDir = System.getProperty("user.home")
        if (homeDir == null || !new File(homeDir).exists())
            throw new RuntimeException("Can't get home directory: $homeDir")
        homeDir
    }

    def getCloudToolsDir() {
        def homeDir = getHomeDir()
        def cloudToolsDir = new File(homeDir, ".cloudtools")
        if (!cloudToolsDir.exists())
            cloudToolsDir.mkdir()
        cloudToolsDir
    }

    def listClusters() {
        def files = []
        cloudToolsDir.eachFileMatch(~/.*\.xml$/) {files << it.name.substring(0, it.name.length() - 4)}
        files
    }

    def makeFile(name) {
        new File(getCloudToolsDir(), name + ".xml")
    }

    def saveDefinition(ClusterDefinition definition) {
        def file = makeFile(definition.name)
        FileWriter fos = new FileWriter(file)
        PrintWriter pw = new PrintWriter(fos)
        buildXml(definition, pw)
        pw.close()
        fos.close()
    }

    def removeCluster(String clusterName) {
        def file = makeFile(clusterName)
        file.delete()
    }


    void writeProperties(thiz, object) {
        BeanInfo info = Introspector.getBeanInfo(object.getClass());
        for (PropertyDescriptor pd: info.getPropertyDescriptors()) {
            if (pd.writeMethod && pd.readMethod && pd.name != "metaClass") {
                String name = pd.name
                Object value = object."${name}"
                if (value != null && !value.equals("")) {
                    if (value instanceof Map) {
                        String singular = name.substring(0, name.size() - 1)
                        thiz."${name}"
                                {
                                    for (Object element in value.entrySet()) {
                                        "${singular}"(element.key + ":" + element.value)
                                    }
                                }
                    } else if (value instanceof List) {
                        String singular = name.substring(0, name.size() - 1)
                        thiz."${name}"
                                {
                                    for (Object element in value) {

                                        if (element.getClass().getName().startsWith("java")) {
                                            "${singular}"(element)
                                        } else {
                                            "${singular}"
                                                    {
                                                        writeProperties(owner, element)
                                                    }

                                        }
                                    }
                                }
                    } else if (value.getClass().getName().startsWith("java"))
                        thiz."${name}"(value)
                    else if (true) {
                        thiz."${name}"
                                {
                                    writeProperties thiz, value
                                }
                    }
                    else
                        throw new RuntimeException("Can't dump class: " + value);
                }
            }
        }
    }

    void populateBean(bean, node) {
        BeanInfo info = Introspector.getBeanInfo(bean.getClass());
        for (PropertyDescriptor pd: info.getPropertyDescriptors()) {
            if (pd.writeMethod && pd.readMethod && pd.name != "metaClass") {
                String name = pd.name
                Class type = pd.getPropertyType()

                if (type == String.class || type == Object.class) {
                    bean."$name" = node."${name}".text()
                } else if (type == List.class) {
                    String singular = name.substring(0, name.size() - 1)
                    def elementClass = ((ParameterizedType) pd.readMethod.getGenericReturnType()).getActualTypeArguments()[0]
                    bean."$name" = node."$name"."$singular".collect {elementNode ->
                        if (elementClass == String.class) {
                            return elementNode.text()
                        } else {
                            def newBean = elementClass.newInstance()
                            populateBean(newBean, elementNode)
                            return newBean
                        }
                    }
                } else if (type == Map.class) {
                    String singular = name.substring(0, name.size() - 1)
                    def map = new HashMap()
                    node."$name"."$singular".each {elementNode ->
                        map.put(* elementNode.text().split("[:]"))
                    }
                    bean."$name" = map
                } else if (type == int.class || type == Integer.class) {
                    def value = node."${name}".text()
                    if (value?.trim())
                        bean."$name" = Integer.parseInt(value)
                } else if (type == boolean.class) {
                    bean."$name" = Boolean.parseBoolean(node."${name}".text())
                } else if (type.name.startsWith("net.chrisrichardson")) {
                    Object newBean = type.newInstance()
                    bean."$name" = newBean
                    populateBean(newBean, node."$name")
                } else
                    throw new RuntimeException("How to handle property: $name, ${type.name}")
            }
        }
    }

    def buildXml(ClusterDefinition definition, PrintWriter pw) {
        def builder = new MarkupBuilder(pw)

        builder.clusterDefinition {
            spec {

                ClusterSpec spec = definition.spec
                version(1)

                writeProperties owner, spec
                /*
                name(definition.name)
                topology(spec.topology)
                numberOfTomcats(spec.numberOfTomcats)
                numberOfSlaves(spec.numberOfSlaves)
                numberOfTerracottaHosts(spec.numberOfTerracottaHosts)
                bucketPrefix(spec.bucketPrefix)
                masterDatabaseInstanceType(spec.masterDatabaseInstanceType)
                slaveDatabaseInstanceType(spec.slaveDatabaseInstanceType)
                tomcatInstanceType(spec.tomcatInstanceType)
                apacheInstanceType(spec.apacheInstanceType)
                jmeterInstanceType(spec.jmeterInstanceType)
                
                webApps {
                    for (WebApp w in spec.webApps) {
                        webApp {
                            path(w.explodedWar)
                            context(w.context)
                        }
                    }
                }
                if (spec.catalinaOptsBuilder)
                    catalinaOptsBuilder(spec.catalinaOptsBuilder)
                schema {
                    SchemaSpecification schema = spec.schemaSpec
                    name(schema.name)
                    users {
                        for (u in schema.users) {
                            user {
                                id(u.key)
                                password(u.value)
                            }
                        }
                    }
                    scripts {
                        for (s in schema.scripts) {
                            script(s)
                        }

                    }

                }
                */
                //            def schemaSpec
                //            int numberOfSlaves = 0
            }
            state {
                for (s in definition.serverState) {
                    serverGroup {
                        name(s.key)
                        servers {
                            for (svr in s.value) {
                                server(svr)
                            }
                        }
                    }
                }
            }

        }
    }



    public ClusterDefinition findDefinition(clusterName) {
        def file = makeFile(clusterName)
        def parser = new XmlParser(false, false)
        makeClusterDefinition(parser.parse(new FileInputStream(file)))
    }

    def makeClusterDefinition(Node node) {
        new ClusterDefinition(node.name.text(), makeSpecV1(node.spec), makeState(node.state))
    }

    def makeSpecV0(node) {
        ClusterSpec spec = new ClusterSpec()
        spec."topology" = node."topology".text()
        spec.tomcats(Integer.parseInt(node.numberOfTomcats.text()))
        spec.terracottaHosts(Integer.parseInt(node.numberOfTerracottaHosts.text()))
        spec.slaves(Integer.parseInt(node.numberOfSlaves.text()))
        node.webApps*.each {webApp -> spec.webApp(webApp.path.text(), webApp.context.text())}
        spec.catalinaOptsBuilder(node.catalinaOptsBuilder.text())

        spec.masterDatabaseInstanceType(node.masterDatabaseInstanceType.text())
        spec.slaveDatabaseInstanceType(node.slaveDatabaseInstanceType.text())
        spec.tomcatInstanceType(node.tomcatInstanceType.text())
        spec.apacheInstanceType(node.apacheInstanceType.text())
        spec.jmeterInstanceType(node.jmeterInstanceType.text())
        spec.bucketPrefix(node.bucketPrefix.text())

        spec.schema(node.schema.name.text(),
                node.schema.users.user.inject([:]) {users, u -> users[u.id.text()] = u.password.text(); users},
                node.schema.scripts.script*.text())
        spec
    }

    ClusterSpec makeSpecV1(node) {
        ClusterSpec spec = new ClusterSpec()
        String version = node.version.text()
        if (version == "1")
            populateBean(spec, node)
        else {
            spec."topology" = node."topology".text()
            spec.tomcats(Integer.parseInt(node.numberOfTomcats.text()))
            spec.terracottaHosts(Integer.parseInt(node.numberOfTerracottaHosts.text()))
            spec.slaves(Integer.parseInt(node.numberOfSlaves.text()))
            node.webApps*.each {webApp -> spec.webApp(webApp.path.text(), webApp.context.text())}
            spec.catalinaOptsBuilder(node.catalinaOptsBuilder.text())

            spec.masterDatabaseInstanceType(node.masterDatabaseInstanceType.text())
            spec.slaveDatabaseInstanceType(node.slaveDatabaseInstanceType.text())
            spec.tomcatInstanceType(node.tomcatInstanceType.text())
            spec.apacheInstanceType(node.apacheInstanceType.text())
            spec.jmeterInstanceType(node.jmeterInstanceType.text())
            spec.bucketPrefix(node.bucketPrefix.text())

            spec.schema(node.schema.name.text(),
                    node.schema.users.user.inject([:]) {users, u -> users[u.id.text()] = u.password.text(); users},
                    node.schema.scripts.script*.text())
        }
        spec
    }


    def makeState(node) {
        node.serverGroup.inject([:]) {state, it -> state[it.name.text()] = it.servers.server*.text(); state}
    }
}